package lumis.portal.el;

import lumis.portal.el.commonsel.Coercions;
import lumis.portal.el.commonsel.Constants;
import lumis.portal.el.commonsel.Expression;
import lumis.portal.el.commonsel.ExpressionEvaluatorImpl;
import lumis.portal.el.commonsel.FunctionInvocation;
import lumis.portal.el.commonsel.Logger;
import lumis.portal.el.commonsel.parser.ELParser;
import lumis.portal.el.commonsel.parser.ParseException;
import lumis.portal.el.commonsel.parser.TokenMgrError;

import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;

/**
 * Object that extends the standard ExpressionEvaluatorImpl. This object however does not store
 * expressions in cache.
 * 
 * @version $Revision: 11998 $ $Date: 2010-10-06 17:29:22 -0300 (Wed, 06 Oct 2010) $
 * @since 4.0.9
 */
public class LumisExpressionEvaluatorImpl extends ExpressionEvaluatorImpl
{
	@SuppressWarnings("unchecked")
	public static Object customFunctionInvocationEvalution(FunctionInvocation functionInvocation,
			VariableResolver pResolver, FunctionMapper functions, Logger pLogger) throws ELException
	{
		String functionName = functionInvocation.getFunctionName();
		List argumentList = functionInvocation.getArgumentList();
		boolean arrayArgs = false;
		
	    // if the Map is null, then the function is invalid
	    if (functions == null)
	      pLogger.logError(Constants.UNKNOWN_FUNCTION, functionName);

		// normalize function name
		String prefix = null;
		String localName = null;
		int index = functionName.indexOf(':');
		if (index == -1)
		{
			prefix = "";
			localName = functionName;
		}
		else
		{
			prefix = functionName.substring(0, index);
			localName = functionName.substring(index + 1);
		}

		// ensure that the function's name is mapped
		Method target = (Method) functions.resolveFunction(prefix, localName);
		if (target == null)
			pLogger.logError(Constants.UNKNOWN_FUNCTION, functionName);

		// ensure that the number of arguments matches the number of parameters
		Class[] params = target.getParameterTypes();
		if (params.length == 1 && params[0].isArray())
			arrayArgs = true;
		else if (params.length != argumentList.size())
			pLogger.logError(Constants.INAPPROPRIATE_FUNCTION_ARG_COUNT, functionName, new Integer(params.length), new Integer(argumentList.size()));

		// now, walk through each parameter, evaluating and casting its argument
		Object[] arguments = new Object[argumentList.size()];
		for (int i = 0; i < arguments.length; i++)
		{
			// evaluate
			arguments[i] = ((Expression) argumentList.get(i)).evaluate(pResolver, functions, pLogger);
			// coerce
			if (arrayArgs)
				arguments[i] = Coercions.coerce(arguments[i], params[0].getComponentType(), pLogger);
			else
				arguments[i] = Coercions.coerce(arguments[i], params[i], pLogger);
		}

		// finally, invoke the target method, which we know to be static
		try
		{
			if (arrayArgs)
			{
				Object[] array = (Object[]) Array.newInstance(params[0].getComponentType(), arguments.length);
				for (int i=0; i<arguments.length; i++)
					array[i] = arguments[i];
				return (target.invoke(null, new Object[] {array}));
			}
			else
				return (target.invoke(null, arguments));
		}
		catch (InvocationTargetException ex)
		{
			pLogger.logError(Constants.FUNCTION_INVOCATION_ERROR, ex.getTargetException(), functionName);
			return null;
		}
		catch (Exception ex)
		{
			pLogger.logError(Constants.FUNCTION_INVOCATION_ERROR, ex, functionName);
			return null;
		}
	}
	
	// -------------------------------------
	public Object parseExpressionString(String pExpressionString) throws ELException
	{
		// See if it's an empty String
		if (pExpressionString.length() == 0)
		{
			return "";
		}

		Object ret = null;

		// Parse the expression
		Reader r = new StringReader(pExpressionString);
		ELParser parser = new ELParser(r);
		try
		{
			ret = parser.ExpressionString();
		}
		catch (ParseException exc)
		{
			throw new ELException(exc.getMessage());
		}
		catch (TokenMgrError exc)
		{
			throw new ELException(exc.getMessage());
		}

		return ret;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object evaluate(String expressionString, Class expectedType,
			VariableResolver resolver, FunctionMapper functions)
			throws ELException
	{
		// since the evaluate process is much heavier than contains, processing
		// contains first, so expressions without EL are evaluated much faster.
		if (String.class.equals(expectedType) && !expressionString.contains("${"))
			return expressionString;
		else
			return super.evaluate(expressionString, expectedType, resolver, functions);
	}
}
